<?php
/**
 * 数据包装类：为了方便的使用Zend_Db_Adapter或者Zend_Db_Table，
 *             使用该数据包装类，可以对_REQUEST数据进行检测，找到和数据库
 *             里对应的字段，对该输入字段进行验证，然后再交给数据库对象进
 *             行进一步处理。
 *
 * @author  wuxi dot cn at gmail dot com
 * @version 0.1 2007.12
 */
class wrapper 
{
	/* 表的字段约束, array */
	protected $cs; // cs=constraint
	/* 表的所有字段名, string */
	protected $fn; // field names
	/* 表中可以为空的字段，string, 用|分隔。 */
	protected $nu; // nu=null
	/* 表中字段输出格式 */
	protected $fmt;

	/**
	 * 上次出错字段。string
	 */
	public $field;
	/**
	 * 出错提示。string
	 */
	public $error;

	/**
	 * 构造函数。
	 *
	 * @param fn string, 所有字段名。用'|'分隔。例如：'Detail|Tel|Sex'。
	 * @param cs array, (constraint)数据库表各个字段的约束。
	 *           数组为字段名和正则式对("field_name"=>"regx")格式。
	 * @param nu string, 可为空字段，用'|'分隔。例如：'Detail|Tel|Sex'。
	 * @param fmt array, field=>format, 字段的输出格式，format如下：
	 *            null|"html": 不做处理，直接输出。注意与下面的"pre"区别。
	 *            "pre": 不做处理，直接输出。与"html"的区别是，在形成编辑
	 *                   框的时候，"html"是input, "pre"是textarea。
	 *            "text": 转换html符号。注意与下面的"paragraph"区别。
	 *            "paragraph": 段落，转换html符号，和text的不同是，在形成
	 *                         编辑框的时候，paragraph用的是textarea，
	 *                         text用的是input。
	 *            "time:<opt>": 时间型转换，<opt>为输出的内容，详见php的
	 *                 strftime()函数。例子：
	 *                 "time:%Y年%m月%d日" 输出：2007年12月24日
	 *                 "time:%H时%M分%S秒" 输出：23时05分51秒
	 */
	public function __construct($fn, $cs, $nu, $fmt=array())
	{
		$this->fn=$fn;
		$this->cs=$cs;
		$this->nu=$nu;
		$this->fmt=$fmt;
	}

	/**
	 * 从数组中过滤出表中字段。
	 *
	 * @param data  array, 待处理的数据。
	 * @param limit bool, 筛选结果数目限制，=0为不限制。
	 *
	 * @return array, 过滤后只包含表里出现的字段的数组。
	 */
	public function filter($data, $limit=0)
	{
		$result=array();
		$n=0;
		$limit=$limit==0?9999:$limit;
		foreach ($data as $k => $v) {
			if (preg_match("/\b$k\b/", $this->fn))
				$result[$k]=$v;
			if (++$n>=$limit) break;
		}
		return $result;
	}

	/**
	 * 过滤字段，并检测字段是否符合规范。
	 *
	 * @param data   array, 待检测的数组。
	 * @param ignore bool, 跳过data里没有字段，不提示错误。
	 * 
	 * @return array|int, 成功返回array，失败返回错误码：
	 *         -1: 字段缺失。出错字段：$this->field，出错信息：$this->error
	 *         -2: 字段不符合规范。出错字段、出错信息同上。
	 */
	public function check($data, $ignore=false)
	{
		foreach ($this->cs as $k => $v) {
			if (array_key_exists($k, $data)) {
				if (!preg_match($v, $data[$k])) {
					$this->field=$k;
					$this->error='Checking failed in field "'.$k.'".';
					return -2;
				}
			}
			else if (!$ignore && !preg_match("/\b$k\b/", $this->nu)) {
				$this->field=$k;
				$this->error='Field "'.$k.'" is missing.';
				return -1;
			}
		} // end of foreach

		return $data;
	}

	/**
	 * 通过参数筛选出字段，并形成字符串，以便数据库操作。
	 *
	 * @param data    array, 待处理的数组。
	 * @param prefix  string, 字段的前缀，用于Zend_Db_Adapter。
	 *                例如，前缀可以是：':', 则字段为：':Field'.
	 * @param bracket string, 字段名前后的符号，为了数据库用。
	 *                例如='`'，则每个字段为：`Field`。
	 *
	 * @return string, 输出结果，格式："Field_1, Field_2, ..."。
	 */
	public function field_string(array $data, $bracket='', $prefix='')
	{
		$d=self::filter($data);
		$s='';
		$sep='';
		$b=$bracket;
		$p=$prefix;
		foreach ($d as $k => $v) {
			$s.=$sep.$b.$p.$k.$b;
			$sep=', ';
		}
		return $s;
	}

	// 用于escape和unescape函数。
	static protected $trans=array('<'=>'&lt;', '>'=>'&gt;',
			'\''=>'&#39;', '"'=>'&quot;', '&'=>'&amp;', ' '=>'&nbsp;',
			'/'=>'&#47;', '\n'=>'<br />', '\r'=>'',
			'\t'=>'&nbsp;&nbsp;&nbsp;&nbsp;'
			);

	/**
	 * 特殊符号=>HTML记号转换，类似php中的htmlentities函数。
	 *
	 * @param data string, 待转换的字符串。
	 *
	 * @return string, 转换后的结果。
	 */
	static function escape($data)
	{
		return strtr($data, self::$trans);
	}

	/**
	 * html记号=>特殊符号转换。self::escape()函数的逆函数。
	 *
	 * @param data string, 待转换的字符串。
	 *
	 * @return string, 转换后的结果。
	 */
	static function unescape($data)
	{
		$trans=array_flip(self::$trans);
		return strtr($data, $trans);
	}

	/**
	 * 格式化数据库字段。（主要是为了配合wrapper的ajax模板使用）
	 *
	 * @param id   int|string, 对象的主键值。
	 * @param data array, 待格式化的数据库一行数据。
	 * @param fmt  array, 一些字段的特定格式。array('field'=>'format'...)。
	 * @param opt  array, 可选参数，如下(array里的下标所表示的意义)：
	 *             "tag"=>string: 用来包裹的html标签，默认是span。
	 *             "class"=>string: 标签特定的css class属性，默认为''。
	 *                    注意，所有标签都有名为"wrapper"的css class。
	 *             "quote"=>bool: 内容是否用双引号，默认=true。
	 * @param edit bool, 是否格式化为wrapper的ajax可编辑模板格式。
	 *             =true是，=false否。默认为false。
	 * @param size array, 编辑框大小。元素格式：
	 *             'field'=>array(<width>, <hieght>)。
	 *
	 * @return, array, 格式化以后的数据。
	 */
	function format($id, $data, $opt=null, $fmt=array(),
			$edit=false, $size=array())
	{
		$opt=array_merge(array('tag'=>'span',
								'class'=>'',
								'quote'=>true), $opt);
		$q=$opt['quote']?'"':'\'';
		$s='';
		$ret=array();
		foreach ($data as $k => $v) {
			if (!preg_match("/\b$k\b/", $this->fn))
				continue;

			$textarea=false;

			// format field
			$f=array_key_exists($k, $fmt)?$fmt[$k]:
				array_key_exists($k, $this->fmt)?$this->fmt[$k]:'html';
			switch ($f) {
			case 'html': // don't escape html
				$d=$v;
			case 'pre':
				$textarea=true;
				break;
			case 'text': // escape html
				$d=self::escape($v);
			case 'paragraph':
				$textarea=true;
				break;
			default:
				if (substr($f, 0, 4)=='time') { // time format
					$d=strftime(substr($f, 5), $v);
				}
			} // end of switch

			// editor
			$attr="id={$q}edit_{$k}_{$id}{$q} name={$q}".
					"{$k}{$q} class={$q}wrapper {$opt['class']}{$q} ".
					"style={$q}display:none;";
			if (array_key_exists($k, $size)) {
				$tmp=$size[$k];
				$attr.="width:{$tmp[0]}; height:{$tmp[1]};";
			}
			$attr.=$q;

			if ($textarea)
				$s.="<textarea {$attr}>$v</textarea>\n";
			else
				$s.="<input {$attr} value={$q}$v{$q} />\n";

			// text
			$s.="<{$opt['tag']} id={$q}{$k}_{$id}{$q} class={$q}".
				$opt['class']."{$q} >{$d}</{$opt['tag']}>\n";

			$ret[$k]=$s;
		} // end of foreach
	}


};
?>
